// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Net.Sockets
{
    internal static class SocketErrorPal
    {
        // No Interop.Errors are included for the following SocketErrors, as there's no good mapping:
        // - SocketError.NoRecovery
        // - SocketError.NotInitialized
        // - SocketError.ProcessLimit
        // - SocketError.SocketError
        // - SocketError.SystemNotReady
        // - SocketError.TypeNotFound
        // - SocketError.VersionNotSupported

        internal static SocketError GetSocketErrorForNativeError(Interop.Error errno) => errno switch
        {
            Interop.Error.EACCES => SocketError.AccessDenied,
            Interop.Error.EADDRINUSE => SocketError.AddressAlreadyInUse,
            Interop.Error.EADDRNOTAVAIL => SocketError.AddressNotAvailable,
            Interop.Error.EAFNOSUPPORT => SocketError.AddressFamilyNotSupported,
            Interop.Error.EAGAIN => SocketError.WouldBlock,
            Interop.Error.EALREADY => SocketError.AlreadyInProgress,
            Interop.Error.EBADF => SocketError.OperationAborted,
            Interop.Error.ECANCELED => SocketError.OperationAborted,
            Interop.Error.ECONNABORTED => SocketError.ConnectionAborted,
            Interop.Error.ECONNREFUSED => SocketError.ConnectionRefused,
            Interop.Error.ECONNRESET => SocketError.ConnectionReset,
            Interop.Error.EDESTADDRREQ => SocketError.DestinationAddressRequired,
            Interop.Error.EFAULT => SocketError.Fault,
            Interop.Error.EHOSTDOWN => SocketError.HostDown,
            Interop.Error.ENXIO => SocketError.HostNotFound, // not perfect, but closest match available
            Interop.Error.EHOSTUNREACH => SocketError.HostUnreachable,
            Interop.Error.EINPROGRESS => SocketError.InProgress,
            Interop.Error.EINTR => SocketError.Interrupted,
            Interop.Error.EINVAL => SocketError.InvalidArgument,
            Interop.Error.EISCONN => SocketError.IsConnected,
            Interop.Error.EMFILE => SocketError.TooManyOpenSockets,
            Interop.Error.EMSGSIZE => SocketError.MessageSize,
            Interop.Error.ENETDOWN => SocketError.NetworkDown,
            Interop.Error.ENETRESET => SocketError.NetworkReset,
            Interop.Error.ENETUNREACH => SocketError.NetworkUnreachable,
            Interop.Error.ENFILE => SocketError.TooManyOpenSockets,
            Interop.Error.ENOBUFS => SocketError.NoBufferSpaceAvailable,
            Interop.Error.ENODATA => SocketError.NoData,
            Interop.Error.ENOENT => SocketError.AddressNotAvailable,
            Interop.Error.ENOPROTOOPT => SocketError.ProtocolOption,
            Interop.Error.ENOTCONN => SocketError.NotConnected,
            Interop.Error.ENOTSOCK => SocketError.NotSocket,
            Interop.Error.ENOTSUP => SocketError.OperationNotSupported,
            Interop.Error.EPERM => SocketError.AccessDenied,
            Interop.Error.EPIPE => SocketError.Shutdown,
            Interop.Error.EPFNOSUPPORT => SocketError.ProtocolFamilyNotSupported,
            Interop.Error.EPROTONOSUPPORT => SocketError.ProtocolNotSupported,
            Interop.Error.EPROTOTYPE => SocketError.ProtocolType,
            Interop.Error.ESOCKTNOSUPPORT => SocketError.SocketNotSupported,
            Interop.Error.ESHUTDOWN => SocketError.Disconnecting,
            Interop.Error.SUCCESS => SocketError.Success,
            Interop.Error.ETIMEDOUT => SocketError.TimedOut,
            _ => SocketError.SocketError, // unknown native error, just treat it as a generic SocketError
        };

        private static Interop.Error GetNativeErrorForSocketErrorHelper(SocketError error) => error switch
        {
            // This is *mostly* an inverse mapping of GetSocketErrorForNativeError.  However, some options have multiple mappings and thus
            // can't be inverted directly.  Other options don't have a mapping from native to SocketError, but when presented with a SocketError,
            // we want to provide the closest relevant Error possible, e.g. EINPROGRESS maps to SocketError.InProgress, and vice versa, but
            // SocketError.IOPending also maps closest to EINPROGRESS.  As such, roundtripping won't necessarily provide the original value 100% of the time,
            // but it's the best we can do given the mismatch between Interop.Error and SocketError.

            SocketError.AccessDenied => Interop.Error.EACCES, // could also have been EPERM
            SocketError.AddressAlreadyInUse => Interop.Error.EADDRINUSE,
            SocketError.AddressNotAvailable => Interop.Error.EADDRNOTAVAIL,
            SocketError.AddressFamilyNotSupported => Interop.Error.EAFNOSUPPORT,
            SocketError.AlreadyInProgress => Interop.Error.EALREADY,
            SocketError.ConnectionAborted => Interop.Error.ECONNABORTED,
            SocketError.ConnectionRefused => Interop.Error.ECONNREFUSED,
            SocketError.ConnectionReset => Interop.Error.ECONNRESET,
            SocketError.DestinationAddressRequired => Interop.Error.EDESTADDRREQ,
            SocketError.Disconnecting => Interop.Error.ESHUTDOWN,
            SocketError.Fault => Interop.Error.EFAULT,
            SocketError.HostDown => Interop.Error.EHOSTDOWN,
            SocketError.HostNotFound => Interop.Error.EHOSTNOTFOUND,
            SocketError.HostUnreachable => Interop.Error.EHOSTUNREACH,
            SocketError.InProgress => Interop.Error.EINPROGRESS,
            SocketError.Interrupted => Interop.Error.EINTR,
            SocketError.InvalidArgument => Interop.Error.EINVAL,
            SocketError.IOPending => Interop.Error.EINPROGRESS,
            SocketError.IsConnected => Interop.Error.EISCONN,
            SocketError.MessageSize => Interop.Error.EMSGSIZE,
            SocketError.NetworkDown => Interop.Error.ENETDOWN,
            SocketError.NetworkReset => Interop.Error.ENETRESET,
            SocketError.NetworkUnreachable => Interop.Error.ENETUNREACH,
            SocketError.NoBufferSpaceAvailable => Interop.Error.ENOBUFS,
            SocketError.NoData => Interop.Error.ENODATA,
            SocketError.NotConnected => Interop.Error.ENOTCONN,
            SocketError.NotSocket => Interop.Error.ENOTSOCK,
            SocketError.OperationAborted => Interop.Error.ECANCELED,
            SocketError.OperationNotSupported => Interop.Error.ENOTSUP,
            SocketError.ProtocolFamilyNotSupported => Interop.Error.EPFNOSUPPORT,
            SocketError.ProtocolNotSupported => Interop.Error.EPROTONOSUPPORT,
            SocketError.ProtocolOption => Interop.Error.ENOPROTOOPT,
            SocketError.ProtocolType => Interop.Error.EPROTOTYPE,
            SocketError.Shutdown => Interop.Error.EPIPE,
            SocketError.SocketNotSupported => Interop.Error.ESOCKTNOSUPPORT,
            SocketError.Success => Interop.Error.SUCCESS,
            SocketError.TimedOut => Interop.Error.ETIMEDOUT,
            SocketError.TooManyOpenSockets => Interop.Error.ENFILE, // could also have been EMFILE
            SocketError.TryAgain => Interop.Error.EAGAIN, // not a perfect mapping, but better than nothing
            SocketError.WouldBlock => Interop.Error.EAGAIN,
            SocketError.SocketError => Interop.Error.ESOCKETERROR,
            _ => Interop.Error.SUCCESS, // default for unknown mappings
        };

        internal static Interop.Error GetNativeErrorForSocketError(SocketError error)
        {
            Interop.Error errno = GetNativeErrorForSocketErrorHelper(error);
            if (errno == Interop.Error.SUCCESS)
            {
                // Use the SocketError's value, as it at least retains some useful info
                errno = (Interop.Error)(int)error;
            }
            return errno;
        }

        internal static bool TryGetNativeErrorForSocketError(SocketError error, out Interop.Error errno)
        {
            errno = GetNativeErrorForSocketErrorHelper(error);
            return errno != Interop.Error.SUCCESS;
        }
    }
}
